package br.com.base.persistencia.filter.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.persistence.Query;

import br.com.base.business.impl.util.UtilStrings;
import br.com.base.model.impl.Entidade;
import br.com.base.persistencia.filter.IFiltro;
import br.com.base.persistencia.filter.impl.util.ClausulaAssociativaSQLEnum;
import br.com.base.persistencia.filter.impl.util.ClausulaComparativaSQLEnum;
import br.com.base.persistencia.filter.impl.util.ClausulaFuncaoSQL;
import br.com.base.persistencia.filter.impl.util.ClausulaOrderSQLEnum;
import br.com.base.persistencia.filter.impl.util.FuncaoSQLEnum;
import br.com.base.persistencia.filter.impl.util.ParametroFiltro;
import br.com.base.persistencia.filter.impl.util.ParametroOrder;

/**
 * @author Milton Domingues
 */
public abstract class Filtro<ENTIDADE extends Entidade<ID>, ID extends Serializable> implements IFiltro {

	private static final long serialVersionUID = -7702655567902368313L;

	private String alias;

	private Class<ENTIDADE> classEntidade;
	private List<ParametroFiltro> parametros;
	private List<ParametroOrder> parametrosOrder;
	private List<String> camposRetornados;

	private ENTIDADE entidade;

	private Integer inicio;
	private Integer quantidade;
	private Long totalResultado;

	public Filtro(String alias) {
		this.alias = alias;
		inicializarFiltro();
	}

	public void addBetween(String coluna, Object objeto1, Object objeto2) {
		this.addBetween(coluna, objeto1, objeto2, ClausulaAssociativaSQLEnum.AND);
	}

	public void addBetween(String coluna, Object objeto1, Object objeto2, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, objeto1, objeto2, ClausulaComparativaSQLEnum.BETWEEN, associativa));
	}

	public void addEquals(String coluna, Object objeto) {
		this.addEquals(coluna, objeto, ClausulaAssociativaSQLEnum.AND);
	}

	public void addEquals(String coluna, Object objeto, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, objeto, ClausulaComparativaSQLEnum.EQUALS, associativa));
	}

	public void addEqualsIgnoreCase(String coluna, String valor){
		this.parametros.add(new ParametroFiltro(coluna, valor, FuncaoSQLEnum.UPPER, ClausulaComparativaSQLEnum.EQUALS, ClausulaAssociativaSQLEnum.AND));
	}

	public void addEqualsStringValid(String coluna, String valor){
		if(!UtilStrings.isNullOrEmpty(valor)){
			this.addEquals(coluna, valor.trim());
		}
	}

	public void addEqualsStringValid(String coluna, String valor, ClausulaAssociativaSQLEnum associativa){
		if(!UtilStrings.isNullOrEmpty(valor)){
			this.addEquals(coluna, valor.trim(), associativa);
		}
	}

	public void addGt(String coluna, String value) {
		addGt(coluna, value, ClausulaAssociativaSQLEnum.AND);
	}

	public void addGt(String coluna, String value, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, value, ClausulaComparativaSQLEnum.GREATER_THAN, associativa));
	}

	public void addIn(String coluna, List<Serializable> list){
		addIn(coluna, list, ClausulaAssociativaSQLEnum.AND);
	}

	public void addIn(String coluna, List<Serializable> list, ClausulaAssociativaSQLEnum associativa){
		this.parametros.add(new ParametroFiltro(coluna, list, ClausulaComparativaSQLEnum.IN, associativa));
	}

	public void addIsNull(String coluna) {
		this.addIsNull(coluna, ClausulaAssociativaSQLEnum.AND);
	}

	public void addIsNull(String coluna, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, ClausulaFuncaoSQL.IS_NULL, associativa));
	}

	public void addLike(String coluna, String objeto) {
		this.addLike(coluna, objeto, ClausulaAssociativaSQLEnum.AND);
	}

	public void addLike(String coluna, String objeto, ClausulaAssociativaSQLEnum associativa) {
		StringBuilder value = new StringBuilder();
		value.append("%");
		value.append(objeto);
		value.append("%");
		this.parametros.add(new ParametroFiltro(coluna, value.toString().trim(), FuncaoSQLEnum.UPPER, ClausulaComparativaSQLEnum.LIKE, associativa));
	}

	public void addLikeStringValid(String coluna, String valor) {
		if(!UtilStrings.isNullOrEmpty(valor)){
			this.addLike(coluna, valor.trim(), ClausulaAssociativaSQLEnum.AND);
		}
	}

	public void addLikeStringValid(String coluna, String valor, ClausulaAssociativaSQLEnum associativa) {
		if(!UtilStrings.isNullOrEmpty(valor)){
			this.addLike(coluna, valor, associativa);
		}
	}

	public void addLt(String coluna, String value) {
		addLt(coluna, value, ClausulaAssociativaSQLEnum.AND);
	}

	public void addLt(String coluna, String value, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, value, ClausulaComparativaSQLEnum.LESS_THAN, associativa));
	}

	public void addNotEquals(String coluna, Object objeto) {
		this.addNotEquals(coluna, objeto, ClausulaAssociativaSQLEnum.AND);
	}

	public void addNotEquals(String coluna, Object objeto, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, objeto, ClausulaComparativaSQLEnum.NOT_EQUALS, associativa));
	}

	public void addNotIn(String coluna, List<Serializable> list){
		addNotIn(coluna, list, ClausulaAssociativaSQLEnum.AND);
	}

	public void addNotIn(String coluna, List<Serializable> list, ClausulaAssociativaSQLEnum associativa){
		this.parametros.add(new ParametroFiltro(coluna, list, ClausulaComparativaSQLEnum.NOT_IN, associativa));
	}

	public void addNotNull(String coluna) {
		this.addNotNull(coluna, ClausulaAssociativaSQLEnum.AND);
	}

	public void addNotNull(String coluna, ClausulaAssociativaSQLEnum associativa) {
		this.parametros.add(new ParametroFiltro(coluna, ClausulaFuncaoSQL.IS_NOT_NULL, associativa));
	}

	@Override
	public void addOrder(String coluna){
		this.parametrosOrder.add(new ParametroOrder(coluna, ClausulaOrderSQLEnum.ASC));
	}

	@Override
	public void addOrder(String coluna, ClausulaOrderSQLEnum order){
		this.parametrosOrder.add(new ParametroOrder(coluna, order));
	}

	private void addSqlOrder(StringBuilder hql){

		if(this.getParametrosOrder().size() > 0){

			hql.append(" ORDER BY ");

			boolean primeiro = true;
			for (ParametroOrder para : this.getParametrosOrder()) {
				if(!primeiro){
					hql.append(", ");
				}
				hql.append(para.getColuna());
				hql.append(" ");
				hql.append(para.getOrder());
				hql.append(" ");
				primeiro = false;
			}
		}
	}

	private void addSqlWhere(StringBuilder hql) {

		for (ParametroFiltro para : this.getParametros()) {

			hql.append(para.getAssociativa());
			hql.append(" ");

			// Clausula com 1 parametro
			if((para.getFuncaoParam() != null) && para.getFuncaoParam().getAplicadoAliasValor()){
				hql.append(para.getFuncaoParam().getDescricao());
			}
			hql.append("(");
			hql.append(this.getAlias());
			hql.append(".");
			hql.append(para.getColuna());
			hql.append(") ");

			if (para.getFuncao() == null) {

				hql.append(para.getComparativa());
				hql.append(" ");

				if(para.getFuncaoParam() != null){
					hql.append(para.getFuncaoParam().getDescricao());
				}
				hql.append("(:");
				hql.append(para.getColuna().replace(".", ""));
				hql.append(") ");

				// Clausula com 2 parâmetros
				if ((para.getComparativa() != null) && para.getComparativa().equals(ClausulaComparativaSQLEnum.BETWEEN)) {
					hql.append(ClausulaAssociativaSQLEnum.AND);
					hql.append(" (:");
					hql.append(para.getColuna().replace(".", ""));
					hql.append("2)");
				}

			} else {
				hql.append(para.getFuncao().getDescricao());
			}

		}
	}

	@Override
	public void clear(){
		this.getParametros().clear();
		this.getParametrosOrder().clear();
	}

	@Override
	public String getAlias() {
		return this.alias;
	}

	public Collection<String> getCamposRetornados() {
		return this.camposRetornados;
	}

	public Class<ENTIDADE> getClassEntidade() {
		return this.classEntidade;
	}

	public ENTIDADE getEntidade() {
		return this.entidade;
	}

	@Override
	public String getHql() {

		StringBuilder hql = new StringBuilder();

		// HQL com os campos retornados
		if(!getCamposRetornados().isEmpty()){
			hql.append("SELECT ");
			hql.append("new ");
			hql.append(this.getTargetClass());
			hql.append("(");
			for (String campos : getCamposRetornados()) {
				hql.append(campos);
				hql.append(",");
			}
			// Remove a ultima virgula do caracter
			hql.deleteCharAt(hql.length() - 1);

			hql.append(")");
		}

		hql.append("FROM ");
		hql.append(this.getTargetClass());
		hql.append(" ");
		hql.append(this.getAlias());
		hql.append(" WHERE 1=1 ");

		// Adiciona clausula hql
		this.addSqlWhere(hql);

		// Adiciona orders
		this.addSqlOrder(hql);

		return hql.toString();
	}

	@Override
	public String getHqlCount() {
		StringBuilder hql = new StringBuilder();

		hql.append("SELECT ");
		hql.append("COUNT(*) ");
		hql.append("FROM ");
		hql.append(this.getTargetClass());
		hql.append(" ");
		hql.append(this.getAlias());
		hql.append(" WHERE 1=1 ");

		// Adiciona clausula hql
		this.addSqlWhere(hql);

		return hql.toString();
	}

	@Override
	public Integer getInicio() {
		return this.inicio;
	}

	public Collection<ParametroFiltro> getParametros() {
		return this.parametros;
	}

	public Collection<ParametroOrder> getParametrosOrder() {
		return this.parametrosOrder;
	}

	@Override
	public Integer getQuantidade() {
		return this.quantidade;
	}

	private String getTargetClass() {
		return this.getClassEntidade().getSimpleName();
	}

	@Override
	public Long getTotalResultado() {
		return this.totalResultado;
	}

	@SuppressWarnings("unchecked")
	private void inicializarEntidadeConsulta(){
		this.classEntidade = (Class<ENTIDADE>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	private void inicializarFiltro(){

		this.parametros = new ArrayList<ParametroFiltro>();
		this.parametrosOrder = new ArrayList<ParametroOrder>();
		inicializarEntidadeConsulta();
		setCamposRetornados(new ArrayList<String>());

		try{
			setEntidade(getClassEntidade().newInstance());
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	@Override
	public void limparCampos() {
		inicializarEntidadeConsulta();
	}

	public void setAlias(String alias) {
		this.alias = alias;
	}

	public void setCamposRetornados(List<String> camposRetornados) {
		this.camposRetornados = camposRetornados;
	}

	public void setClassEntidade(Class<ENTIDADE> classEntidade) {
		this.classEntidade = classEntidade;
	}

	public void setEntidade(ENTIDADE entidade) {
		this.entidade = entidade;
	}

	@Override
	public void setInicio(Integer inicio) {
		this.inicio = inicio;
	}

	public void setParametros(List<ParametroFiltro> parametros) {
		this.parametros = parametros;
	}

	@Override
	public void setParametros(Query query) {

		for (ParametroFiltro para : this.getParametros()) {
			if (para.getParametro() != null) {

				query.setParameter(para.getColuna().replace(".", ""), para.getParametro());

				if (para.getComparativa().equals(ClausulaComparativaSQLEnum.BETWEEN)) {
					StringBuilder coluna = new StringBuilder();
					coluna.append(para.getColuna().replace(".", ""));
					coluna.append("2");
					query.setParameter(coluna.toString(), para.getParametro2());
				}
			}
		}
	}

	public void setParametrosOrder(List<ParametroOrder> parametrosOrder) {
		this.parametrosOrder = parametrosOrder;
	}

	@Override
	public void setQuantidade(Integer quantidade) {
		this.quantidade = quantidade;
	}

	@Override
	public void setTotalResultado(Long totalResultado) {
		this.totalResultado = totalResultado;
	}

}
